#include <linux/module.h>  // 包含模块的头文件
#include <linux/fs.h>      // 包含文件操作的头文件
#include <asm/uaccess.h>   // 包含访问用户空间数据的头文件

/*  对象，主从设备号（12位主，20位从），所属模块，设备名*/
#define DEVICE_NAME "simple_char_dev" // 设备名

// 原型函数声明
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);

// 全局变量
static int Major;             // 主设备号
static int Device_Open = 0;   // 设备被打开的次数

// 文件操作结构体
static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

// 初始化模块
static int __init init_module(void) {
    Major = register_chrdev(0, DEVICE_NAME, &fops);

    if (Major < 0) {
        printk(KERN_ALERT "注册字符设备失败，设备号: %d\n", Major);
        return Major;
    }

    printk(KERN_INFO "注册字符设备成功，设备号: %d\n", Major);
    printk(KERN_INFO "创建设备节点: mknod /dev/%s c %d 0\n", DEVICE_NAME, Major);

    return 0;
}

// 清理模块
static void __exit exit_module(void) {
    unregister_chrdev(Major, DEVICE_NAME);
    printk(KERN_INFO "注销字符设备: %s\n", DEVICE_NAME);
}

// 打开设备
static int device_open(struct inode *inode, struct file *file) {
    if (Device_Open)
        return -EBUSY;

    Device_Open++;
    try_module_get(THIS_MODULE);
    return 0;
}

// 释放设备
static int device_release(struct inode *inode, struct file *file) {
    Device_Open--;

    module_put(THIS_MODULE);
    return 0;
}

// 从设备读取数据
static ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) {
    // 在这里添加读取逻辑
    return -EINVAL;
}

// 向设备写入数据
static ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t *off) {
    // 在这里添加写入逻辑
    return -EINVAL;
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sjianzhao");
MODULE_DESCRIPTION("A Simple Character Device Driver");







/*
struct cdev{
    struct kobject kobj;
    struct module *owner;           //  指向模块的指针，用于标识该字符设备驱动所属的模块
    struct file_operations *fops;   //  指向文件操作结构的指针，定义了字符设备驱动对文件操作的回调函数，如读、写、打开、关闭等
    struct list_head list;          //  用于将字符设备驱动添加到字符设备驱动链表中
    dev_t dev;                      //  设备号
    unsigned int count;
};

struct fops{
    struct module *owner;
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    int (*llseek) (struct file *, loff_t, int);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    ... ...
}

cdev 就可以代表一个字符设备，它包含了设备号、文件操作结构体等信息，通过cdev_add函数将cdev注册到系统中，就可以使用mknod命令创建设备节点，
然后就可以通过设备节点与字符设备进行交互了。

1.cdev 如何与 file_operations 建立联系   void cdev_init(&cdev, &fops);    
2.如何动态申请一个cdev内存  struct cdev* cdev_alloc();
3.如何注册一个cdev  int cdev_add(struct cdev *p, dev_t dev, unsigned count);
4.如何注销一个cdev  void cdev_del(struct cdev *p);

5.同时在注册字符设备时，需要先申请设备号，可以使用以下函数：
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);   //用于设备号未知的情况
或则
int register_chrdev_region(dev_t dev, unsigned count, const char *name);  // 用于已知起始设备号的情况



linux 字符设备的驱动组成：
1.模块加载函数
2.模块卸载函数
3.字符设备结构体
4.文件操作结构体
5.设备操作函数
6.模块许可证信息

THIS_MODULE 是 Linux 内核编程中的一个宏，用于获取当前模块的指针


字符设备的 file_operations 结构体中，包含了各种文件操作函数的指针，如 read、write、open、release 等。
这些函数指针指向具体的设备操作函数，当用户通过文件系统对字符设备进行操作时，这些函数指针会被调用，从而实现具体的设备操作。
其中注意内核与用户空间的交互，需要使用copy_to_user和copy_from_user函数
copy_to_user ：将数据从内核空间复制到用户空间
copy_from_user ：将数据从用户空间复制到内核空间


*/

#define CDEV_NAME  "charDevName"

struct _xx_dev_t {
    struct cdev cdev;

}xxx_dev_t;
/* 用于绑定对字符设备操作的一些接口 ；然后通过 cdev_init 函数，将操作绑定到 cdev 上 */
struct file_operations xxx_fops = {
    .owner = THIS_MODULE,
    .open = device_open,
    .release = device_release,
    .read = device_read,
    .write = device_write,
};

int device_open(struct inode *inode, struct file *file);
int device_release(struct inode *inode, struct file *file);
ssize_t device_read(struct file *filp, char *buffer, size_t length, loff_t *offset);
ssize_t device_write(struct file *filp, const char *buff, size_t len, loff_t *off);

int device_open(struct inode *inode, struct file *file) {
    return 0;
}

int device_release(struct inode *inode, struct file *file) {
    return 0;
}

int device_read(struct file *filp, char *buffer, size_t length, loff_t *offset) {
    return 0;
}

int device_write(struct file *filp, const char *buff, size_t len, loff_t *off) {
    return 0;
}


/* 加载设备驱动模块 */
/* 绑定 fops的操作回调、标识模块归属、申请设备号、注册字符设备*/
static int __init xxx_init(void)
{
    int ret = 0;
    xxx_dev_t *dev_s = NULL;

    dev_s = kzalloc(sizeof(xxx_dev_t), GFP_KERNEL);
    if (!dev_s) {
        printk(KERN_ERR "kzalloc xxx_dev_t failed\n");
        return -ENOMEM;
    }

    cdev_init(&dev_s->cdev, &xxx_fops);
    dev_s->cdev.owner = xxx_fops.owner;    //  指向模块的指针，用于标识该字符设备驱动所属的模块；在模块加载时被初始化，并在模块卸载时被销毁
    alloc_chrdev_region(&dev_s->cdev.dev, 0, 1, CDEV_NAME);  //  申请设备号
    ret = cdev_add(&dev_s->cdev, dev_s->cdev.dev, 1);  //  注册字符设备
    if (ret < 0) {
        printk(KERN_ERR "cdev_add failed\n");
        unregister_chrdev_region(dev_s->cdev.dev, 1);
        kfree(dev_s);
        return ret;
    }

    return 0;
}

/*卸载字符设备驱动*/
static void __exit xxx_exit(void)
{
    cdev_del(&xxx_dev_t->cdev);  //  注销字符设备
    unregister_chrdev_region(xxx_dev_t->cdev.dev, 1);  //  释放设备号
    kfree(xxx_dev_t);

}



/*
globalmem 虚拟设备 ： 
作用：用于测试字符设备驱动程序，它是一个虚拟设备，没有实际的硬件支持，但是可以通过文件系统对其进行访问和操作。

ioctl函数：用于对设备进行控制操作，例如设置设备参数、获取设备状态等。在ioctl函数中，可以根据传入的命令参数，执行相应的操作。
其中 ioctl的命令也有讲究：
1. 高八位是设备类型，低八位是序列号，以及2位的方向，14位的数据尺寸
2. 设备类型的8位成为幻数，必须唯一，可以通过宏定义来生成
3. 序列号，用于区分同一设备类型的多个设备
4. 方向，用于指定数据传输的方向，表示该数据的传输方向可能是以下几种情况： 无数据传输、读、写、双向传输
内核中定义了4个宏来辅助生成命令 ：__IO() __IOR()  __IOW()  __IOWR()

文件的私有数据：在打开文件时，内核会为文件分配一个私有的数据结构，用于存储与该文件相关的信息。在字符设备驱动程序中，
                可以通过file->private_data来访问该数据结构，从而获取与该文件相关的信息。

实际上是将设备结构体指针赋值给文件的私有数据，在文件操作函数中，可以通过file->private_data来获取设备结构体指针，从而访问设备数据。

*/

/*----------------------------------使用私有数据的驱动---------------------------------------------*/
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/cdev.h>
#include<linux/device.h>
#include<linux/uaccess.h>
#include<linux/slab.h>

#define CDEV_NAME  "charDevName"
#define GLOBALMEM_SIZE  0x1000
#define GLOBALMEM_MAJOR  230
#define GLOBALMEM_CLEAR 0x1


static int globalmem_major = GLOBALMEM_MAJOR;
module_param(globalmem_major, int, S_IRUGO);


struct globalmem_dev_t {
    struct cdev dev;
    /* data */
    uint8_t mem[GLOBALMEM_SIZE];
};


struct globalmem_dev_t *globalmem_devp = NULL;

static int globalmem_open(struct inode *inode, struct file *file) {
    file->private_data = globalmem_devp; //  将设备结构体指针赋值给私有数据
    return 0;
}

static int globalmem_release(struct inode *inode, struct file *file) {
    return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) {
    unsigned long mem_offset = *offset;
    unsigned int count = length;
    int ret = 0;
    struct globalmem_dev_t *dev = filp->private_data;

    if (mem_offset >= GLOBALMEM_SIZE) {
        return 0;
    }

    if (mem_offset + count > GLOBALMEM_SIZE) {
        count = GLOBALMEM_SIZE - mem_offset;
    }

    if (copy_to_user(buffer, dev->mem + mem_offset, count)) {
        ret = -EFAULT;
    }
    else {
        *offset += count;
        ret = count;
        printk(KERN_INFO "read %d bytes(s) from %lu\n", count, mem_offset);
    }
    return ret;
}

static ssize_t globalmem_write(struct file *filp, const char *buff, size_t len, loff_t *off) {
    unsigned long mem_offset = *off;
    unsigned int count = len;
    int ret = 0;
    struct globalmem_dev_t *dev = filp->private_data;  // 获取私有数据，这个私有数据指针，不仅有当前设备，还有当前设备的私有数据
    if( mem_offset >= GLOBALMEM_SIZE) {
        return -EINVAL;
    }

    if (mem_offset + count > GLOBALMEM_SIZE) {
        count = GLOBALMEM_SIZE - mem_offset;
    }

    if (copy_from_user(dev->mem + mem_offset, buff, count)) {
        ret = -EFAULT;
    }
    else {
        *off += count;
        ret = count;
        printk(KERN_INFO "write %d bytes(s) from %lu\n", count, mem_offset);
    }
    return ret;
}

static long globalmem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    struct globalmem_dev_t *dev = filp->private_data;  // 获取私有数据,这个私有数据指针，指向的是设备节点
    switch (cmd) {
        case GLOBALMEM_CLEAR:
            memset(dev->mem, 0, GLOBALMEM_SIZE);
            break;
        default:
            return -EINVAL;
    }
    return 0;
}

static llof_t globalmem_llseek(struct file *filp, loff_t offset, int whence) {
    int ret = 0;
    switch (whence) {
        case 0:
            if (offset < 0) {
                ret = -EINVAL;
                break;
            }
            if(offset > GLOBALMEM_SIZE) {
                ret = -EINVAL;  
                break;
            }
            filp->f_pos = offset;    // 设置设备的私有数据偏移量（文件偏移指针）
            break;
        case 1:
            if (filp->f_pos + offset < 0) {
                return -EINVAL;
                break;
            }
            if( filp->f_pos + offset > GLOBALMEM_SIZE) {
                return -EINVAL;
                break;
            }
            filp->f_pos += offset;
            ret = filp->f_pos;
            break;
        default:
            return -EINVAL;
            break;
    }
    return ret;
}

struct file_operations globalmem_fops = {
    .owner = THIS_MODULE,
    .open = globalmem_open,
    .release = globalmem_release,
    .read = globalmem_read,
    .write = globalmem_write,
    .unlocked_ioctl = globalmem_ioctl,
    .llseek = globalmem_llseek,
};

static int __init globalmem_init(void) {
    int ret = 0;
    dev_t dev_major = 0;
    /* 创建设备号 */
    dev_major = MKDEV(globalmem_major, 0);
    if(globalmem_major) {
        ret = register_chrdev_region(dev_major, 1, "globalmem");
    }
    else {
        ret = alloc_chrdev_region(&dev_major, 0, 1, "globalmem");
    }
    if(ret < 0) {
        printk(KERN_INFO "register_chrdev_region failed\n");
        return ret;
    }
    /*为私有数据开辟空间*/
    globalmem_devp = kmalloc(sizeof(struct globalmem_dev_t), GFP_KERNEL);
    if(!globalmem_devp) {
        ret = -ENOMEM;
        unregister_chrdev_region(dev_major, 1);
    }
    memset(globalmem_devp, 0, sizeof(struct globalmem_dev_t));
    globalmem_devp->mem = kmalloc(GLOBALMEM_SIZE, GFP_KERNEL);
    if(!globalmem_devp->mem) {
        ret = -ENOMEM;
        kfree(globalmem_devp);
        unregister_chrdev_region(dev_major, 1);
    }
    /* 对设备绑定操作 */
    cdev_init(&globalmem_devp->cdev, &globalmem_fops);
    globalmem_devp->cdev.owner = THIS_MODULE;
    /*注册设备*/
    ret = cdev_add(&globalmem_devp->cdev, dev_major, 1);
    if(ret < 0) {
        printk(KERN_INFO "cdev_add failed\n");
    }
    return ret;
}

static void __exit globalmem_exit(void) {
    cdev_del(&globalmem_devp->cdev);
    unregister_chrdev_region(globalmem_major, 1);
    kfree(globalmem_devp->mem);
    kfree(globalmem_devp);

}

module_init(globalmem_init);
module_exit(globalmem_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sjianzhao");
MODULE_DESCRIPTION("globalmem char driver");



/*

总结：
   字符设备是3大类设备中的一类，其驱动程序的工作主要是实现文件系统与硬件之间的接口，包括对硬件的初始化、添加和删除cdev结构体，申请和释放设备号，
   以及绑定file_operations结构体的操作函数等。
   字符设备驱动的操作主要有：
   1.初始化和注销字符设备驱动   cdev_init()、cdev_add()、cdev_del()、unregister_chrdev_region()
   2.字符设备驱动读写操作  read()、write()  
   3.绑定读写操作到字符设备上 file_operations
   4.设备号申请 alloc_chrdev_region()、register_chrdev_region()、 MKDEV()
   5.私有数据申请内存 kmalloc()、kfree()、kmalloc_array()、kfree_array()、kzalloc()
   6. 将私有数据以及设备操作绑定到 cdev_t 的结构体上
*/























